import functools
from schema import Schema

from flow_alchemy.core.node import node_dict
from flow_alchemy.core.node import EndNode, StartNode


class Generator:
    def __init__(self, parent):
        self.parent = parent

    def __getattribute__(self, item):
        if item in node_dict:
            cls = node_dict[item]

            def inner(*args, **kwargs):
                node = cls(*args, **kwargs)
                self.parent.push(node)
                return node

            return inner
        return super(Generator, self).__getattribute__(item)


class ControllerForm:
    __form__ = None
    __model__ = None

    def forms(self, session, task) -> list:
        data = self.query(session, task)
        if data:
            return [self.__form__(_.dict) for _ in data]
        return None

    def query(self, task, session) -> list:
        return session.query(self.__model__).filter_by(tid=task.tid).all()

    def save(self, data: list) -> list:
        if data:
            return [self.__model__(_.dict) for _ in data]
        return None


class ControllerData:
    __model__ = None

    def query(self, task, session) -> list:
        return session.query(self.__model__).filter_by(tid=task.tid).all()


class Controller:

    def __init__(self, **kwargs):
        Schema({"id": int, "name": str, 'model': object}, ignore_extra_keys=True).validate(kwargs)
        self.model = kwargs.get('model', None)
        self.name = kwargs.get('name', None)
        self.category = kwargs.get('category', None)
        self.nodes = {}
        self.loads = {}
        self.id = kwargs.get('id', 0)
        if not self.id and self.id % 1000:
            raise ValueError("Invalid %s ID %s" % (self.__class__.__name__, self.id))
        self.start = self.make.StartNode(nid=self.id, name='start')
        self.end = self.make.EndNode(nid=self.id + 999, name='end')

    def push(self, node):
        if node.nid not in self.nodes.keys():
            self.nodes[node.nid] = node
        else:
            # raise Exception(node.id)
            raise TypeError('Node "%s" Can not be added repeatedly%s' % node.name)

    def __getitem__(self, item, default=None):
        return self.nodes.get(item, default)

    def __setitem__(self, key, value):
        self.nodes[key] = value

    def __delitem__(self, key):
        # del self.__nodes[key]
        raise TypeError('Once a target is added, it cannot be deleted')

    def __contains__(self, t):
        return t in self.nodes

    def __iter__(self):
        return self.nodes.__iter__()

    def items(self):
        return self.nodes.items()

    def keys(self):
        return self.nodes.keys()

    @property
    def make(self):
        return Generator(self)

    @property
    def doc(self):
        return self.__doc__

    def validate(self, extra_validators=None):
        start_count = 0
        end_count = 0
        for _ in self.keys():
            node = self[_]
            start_count += 1 if node.__class__ is StartNode else 0
            end_count += 1 if node.__class__ is EndNode else 0
            if (node.__class__ is not EndNode) and (not node.child):
                raise Exception('%s , %s has no child nodes and is not an EndNode.' % (self, node))
        if start_count != 1:
            raise Exception('%s has %s StartNode.' % (self, start_count))
        if end_count != 1:
            raise Exception('%s has %s EndNode.' % (self, end_count))

    def design_data(self) -> dict:
        nodes = []
        edges = []
        for i in self.keys():
            node = self[i]
            nodes.append({'id': node.id, 'label': node.name})
            for child, wrapper in node.child:
                edges.append({'from': node.id,
                              'to': child.id,
                              'id': "%s-%s" % (node.id, child.id),
                              'label': wrapper.__doc__})

        return {'nodes': nodes, 'edges': edges}

    def forms(self, session, task) -> dict:
        form_metas = {k: v for k, v in self.__class__.__dict__.items() if isinstance(v, ControllerForm)}
        return {k: v.forms(session=session, task=task) for k, v in form_metas}

    def dataset(self, session, task) -> dict:
        data_metas = {k: v for k, v in self.__class__.__dict__.items() if isinstance(v, ControllerData)}
        return {k: v.query(session=session, task=task) for k, v in data_metas}

    def loading(self, name, lazy=False, readonly=False):
        if name in ('task', 'ctrl', 'log', 'process_manager', 'extract', 'session', 'context'):
            raise ValueError("name not in ('task','ctrl','log','process_manager','extract','session','context')")

        def decorator(fun):
            @functools.wraps(fun)
            def wrapper(context):
                parameters = context.fill_parameters(fun)
                return fun(**parameters)

            self.loads[name] = {'wrapper': wrapper, 'lazy': lazy, 'readonly': readonly, 'data': None}
            return wrapper

        return decorator

    def __repr__(self):
        result = []
        for node in self.nodes:
            result.append(node.__repr__())
        return str(self)

    def __str__(self):
        return "<%s(name=%s,id=%s)>" % (self.__class__.__name__, self.name, self.id)
